//
// Created by Administrator on 2022/1/16.
// 一个简单的太阳系
// 太阳是Pyramid，地球是大Cube，月球是小Cube，应用了Matrix Stack的idea
//

#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "Common/Utils.h"
#include "glm/glm.hpp"
#include "glm/gtc/type_ptr.hpp"

#include <stack>

#define numVAOs 1
#define numVBOs 2
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint renderingProgram;
std::stack<glm::mat4> mvStack;
glm::vec3 cameraPos(0.0f, 0.0f, 8.0f);
glm::mat4 projMatrix;
glm::mat4 viewMatrix;
float curTime;

void OnWindowSizeCallback(GLFWwindow* window, int w, int h);
void Init(GLFWwindow* window);
void Update(GLFWwindow* window, double currentTime);
void Render();

void SetupVertices();

int main()
{
    if(!glfwInit())
    {
        exit(EXIT_FAILURE);
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(800, 600, "SimpleSolarSystem", nullptr, nullptr);
    if(!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    glfwMakeContextCurrent(window);
    glfwSetWindowSizeCallback(window, OnWindowSizeCallback);

    if(!gladLoadGLLoader(GLADloadproc(glfwGetProcAddress)))
    {
        glfwDestroyWindow(window);
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    Init(window);
    glfwSwapInterval(1);
    while(!glfwWindowShouldClose(window))
    {
        Update(window, glfwGetTime());
        Render();
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}

void OnWindowSizeCallback(GLFWwindow* window, int w, int h)
{
    glViewport(0, 0, w, h);
    projMatrix = glm::perspective(1.0472f, float(w) / float(h), 0.1f, 1000.0f);
}

void Init(GLFWwindow* window)
{
    renderingProgram = Utils::CreateShaderProgram("../../Chapter4/vertShader.glsl",
                                                  "../../Chapter4/fragShader.glsl");
    SetupVertices();

    int w, h;
    glfwGetFramebufferSize(window, &w, &h);
    projMatrix = glm::perspective(1.0472f, float(w) / float(h), 0.1f, 1000.0f);
}

void Update(GLFWwindow* window, double currentTime)
{
    viewMatrix = glm::translate(glm::mat4(1.0f), -cameraPos);

    // 放入第一个矩阵，根矩阵
    mvStack.push(viewMatrix);

    curTime = float(currentTime);
}

void Render()
{
    glClearColor(0.2f, 0.2f, 0.2f, 0.2f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(renderingProgram);
    glUniformMatrix4fv(glGetUniformLocation(renderingProgram, "projMatrix"), 1, GL_FALSE, glm::value_ptr(projMatrix));
    GLint mvMatrixLoc = glGetUniformLocation(renderingProgram, "mvMatrix");

    // 太阳
    mvStack.push(mvStack.top());
    // position（0.0, 0.0, 0.0)
    mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
    mvStack.push(mvStack.top());
    // 绕x轴旋转
    mvStack.top() *= glm::rotate(glm::mat4(1.0f), curTime, glm::vec3(1.0f, 0.0f, 0.0f));

    glUniformMatrix4fv(mvMatrixLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glDrawArrays(GL_TRIANGLES, 0, 18);
    mvStack.pop();

    // 地球
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(sin(curTime) * 4.0f, 0.0f, cos(curTime) * 4.0f));
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::rotate(glm::mat4(1.0f), curTime, glm::vec3(0.0f, 1.0f, 0.0f));

    glUniformMatrix4fv(mvMatrixLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CW);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    mvStack.pop();

    // 月球
    mvStack.push(mvStack.top());
    mvStack.top() *= glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, sin(curTime) * 2.0f, cos(curTime) * 2.0f));
    mvStack.top() *= glm::rotate(glm::mat4(1.0f), curTime, glm::vec3(0.0f, 0.0f, 1.0f));
    mvStack.top() *= glm::scale(glm::mat4(1.0f), glm::vec3(0.25f, 0.25f, 0.25f));

    glUniformMatrix4fv(mvMatrixLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top()));
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CW);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    mvStack.pop();

    mvStack.pop();
    mvStack.pop();
    mvStack.pop();
}

void SetupVertices()
{
    float cubePositions[108] = {
            -1.0f, 1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,

            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,

            1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,

            -1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,

            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,

            -1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, 1.0f, -1.0f
    };

    float pyramidPositions[54] = {
            -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // front face
            1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // right face
            1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // back face
            -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // left face
            -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, // base – left front
            1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f
    };

    glGenVertexArrays(numVAOs, vao);
    glBindVertexArray(vao[0]);
    glGenBuffers(numVBOs, vbo);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubePositions), cubePositions, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidPositions), pyramidPositions, GL_STATIC_DRAW);
}